/*
 * $Id $
 * Copyright: 2008, 2010 Valentin Kolesnikov (javadev75@gmail.com)
 */
package net.sourceforge.jselunit;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.gargoylesoftware.htmlunit.util.NameValuePair;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import static org.junit.Assert.*;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import com.gargoylesoftware.htmlunit.html.FrameWindow;
import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;

/**
 * Base functionality for the HtmlUnit tests.
 *
 * @author Valentyn Kolesnikov
 * @version $Revision$ $Date$
 */
public abstract class AbstractHtmlUnitTest {

    /**
     * Base url for all http requests.
     *
     */
    public static final String BASE_URL = "http://localhost:8080";

    /**
     * Logger.
     */
    protected static final Log LOG = LogFactory
            .getLog(AbstractHtmlUnitTest.class);

    /**
     * Common WebClient for all tests.
     */
    private WebClient webClient;
    /**
     * Common HtmlPage for all tests.
     */
    private HtmlPage page;

    /**
     * Stores visited pages.
     */
    private Stack<Page> pageHistory = new Stack<Page>();

    /**
     * base url.
     */
    private String baseUrl;

    /**
     * Selenium object.
     */
    protected AbstractHtmlUnitTest selenium;

    /**
     * Create and initialize webClient for each test.
     *
     * @throws Exception
     *             create client errors
     */
    @Before
    public void setUp() throws Exception {
        webClient = new WebClient(BrowserVersion.FIREFOX_3);
        webClient.addRequestHeader("Accept",
                "text/javascript, text/html, application/xml, text/xml, */*");
        webClient.setThrowExceptionOnFailingStatusCode(false);
        webClient.setThrowExceptionOnScriptError(false);
        setBaseUrl(BASE_URL);
        selenium = new AbstractHtmlUnitTest() {
        };
        selenium.setWebClient(webClient);
        selenium.setBaseUrl(BASE_URL);
    }

    @After
    public void tearDown() throws Exception {
        if (page != null) {
            echo(getPageSource());
        }
        if (selenium.getPage() != null) {
            echo(selenium.getPageSource());
        }
        webClient.closeAllWindows();
    }

    /**
     * Gets webClient.
     *
     * @return the webClient
     */
    public WebClient getWebClient() {
        return webClient;
    }

    /**
     * Gets page.
     *
     * @return the page
     */
    public HtmlPage getPage() {
        return page;
    }

    /**
     * Sets webClient.
     *
     * @param localWebClient
     *            the webClient to set
     */
    public void setWebClient(WebClient localWebClient) {
        this.webClient = localWebClient;
    }

    /**
     * Uses in login routine.
     *
     * @return the base url address.
     */
    protected String getBaseUrl() {
        return baseUrl;
    }

    /**
     * Uses in login routine.
     *
     * @param baseUrl
     *            the new base url
     */
    protected void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    /**
     * Changes page with history support.
     *
     * @param localPage
     *            the new page
     */
    @SuppressWarnings("deprecation")
    protected void changePage(Page localPage) {
        // Remove old page
        if (pageHistory.size() > 2) {
            pageHistory.remove(0);
        }
        pageHistory.push(localPage);
        if (localPage instanceof HtmlPage) {
            this.page = (HtmlPage) localPage;
        } else {
            this.page = new HtmlPage(
                    localPage.getWebResponse().getRequestUrl(), localPage
                            .getWebResponse(), localPage.getEnclosingWindow());
        }
        final String url = page.getWebResponse().getRequestUrl().toString();
        List<NameValuePair> params = page.getWebResponse().getRequestSettings()
                .getRequestParameters();
        final StringBuilder request = new StringBuilder();
        for (NameValuePair param : params) {
            if (request.length() == 0) {
                request.append("?");
            } else {
                request.append("&");
            }
            request.append(param.getName() + "=" + param.getValue());
        }
        echo(page.getWebResponse().getRequestSettings().getHttpMethod() + ": "
                + url + request.toString());
    }

    /**
     * Reads page for the specified URL.
     *
     * @param url
     *            the specified URL
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void open(String url) throws FailingHttpStatusCodeException,
            MalformedURLException, IOException {
        if (url.startsWith("http://")) {
            changePage((Page) webClient.getPage(url));
        } else {
            changePage((Page) webClient.getPage(getBaseUrl() + url));
        }
    }

    /**
     * Alias for open.
     *
     * @param url
     *            the specified URL
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void beginAt(String url) throws FailingHttpStatusCodeException,
            MalformedURLException, IOException {
        open(url);
    }

    /**
     * Create pairs list.
     *
     * @param params
     *            request params
     * @return the pairs list
     */
    private List<NameValuePair> makePairs(List<String> params) {
        final List<NameValuePair> pairs = new ArrayList<NameValuePair>();
        if (params == null) {
            return pairs;
        }
        int index = 0;
        String key = null;
        for (String param : params) {
            if (index % 2 == 0) {
                key = param;
            } else {
                pairs.add(new NameValuePair(key, param));
            }
            index += 1;
        }
        return pairs;
    }

    /**
     * Reads page for the specified URL with method DELETE.
     *
     * @param url
     *            the specified URL
     * @param params
     *            request params
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void doDelete(String url, List<String> params)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final URL localUrl = new URL(getBaseUrl() + url);
        final WebRequestSettings webRequestSettings = new WebRequestSettings(
                localUrl, HttpMethod.DELETE);
        webRequestSettings.setRequestParameters(makePairs(params));
        changePage((Page) webClient.getPage(webRequestSettings));
    }

    /**
     * Alias for doDelete.
     *
     * @param params
     *            the specified params (url, params)
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void doDelete(String... params)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        if (params.length == 1) {
            doDelete(params[0], null);
        } else if (params.length > 1) {
            final List<String> listParams = new ArrayList<String>(Arrays
                    .asList(params));
            listParams.remove(0);
            doDelete(params[0], listParams);
        } else {
            fail("Usage: doDelete(url, params...)");
        }
    }

    /**
     * Reads page for the specified URL with method GET.
     *
     * @param url
     *            the specified URL
     * @param params
     *            the specified request params
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void doGet(String url, List<String> params)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final URL localUrl = new URL(getBaseUrl() + url);
        WebRequestSettings webRequestSettings = new WebRequestSettings(
                localUrl, HttpMethod.GET);
        webRequestSettings.setRequestParameters(makePairs(params));
        changePage((Page) webClient.getPage(webRequestSettings));
    }

    /**
     * Alias for doGet.
     *
     * @param params
     *            the specified params (url, params)
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void doGet(String... params)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        if (params.length == 1) {
            doGet(params[0], null);
        } else if (params.length > 1) {
            List<String> listParams = new ArrayList<String>(Arrays
                    .asList(params));
            listParams.remove(0);
            doGet(params[0], listParams);
        } else {
            fail("Usage: doGet(url, params...)");
        }
    }

    /**
     * Reads page for the specified URL with method POST.
     *
     * @param url
     *            the specified URL
     * @param params
     *            the specified request params
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void doPost(String url, List<String> params)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        URL localUrl = new URL(getBaseUrl() + url);
        WebRequestSettings webRequestSettings = new WebRequestSettings(
                localUrl, HttpMethod.POST);
        webRequestSettings.setRequestParameters(makePairs(params));
        changePage((Page) webClient.getPage(webRequestSettings));
    }

    /**
     * Alias for doPost.
     *
     * @param params
     *            the specified params (url, params)
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void doPost(String... params)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        if (params.length == 1) {
            doPost(params[0], null);
        } else if (params.length > 1) {
            List<String> listParams = new ArrayList<String>(Arrays
                    .asList(params));
            listParams.remove(0);
            doPost(params[0], listParams);
        } else {
            fail("Usage: doPost(url, params...)");
        }
    }

    /**
     * Fills the input fields in the form.
     *
     * @param formName
     *            the name of the form
     * @param fieldName
     *            the name of the field
     * @param newValue
     *            the new value for the field
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void type(String formName, String fieldName, String newValue)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final HtmlForm form = page.getFormByName(formName);

        final HtmlInput inputField = form.getInputByName(fieldName);
        inputField.setValueAttribute(newValue);
    }

    /**
     * Fills the input html elements on the page. Works only with input and
     * textarea elements.
     *
     * @param xpath
     *            the XPath
     * @param newValue
     *            the new value
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void type(String xpath, String newValue)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        if (h1 instanceof HtmlTextInput || h1 instanceof HtmlPasswordInput
                || h1 instanceof HtmlFileInput) {
            h1.setAttribute("value", newValue);
        } else if (h1 instanceof HtmlTextArea) {
            h1.setTextContent(newValue);
        } else {
            fail("Type method not supported for element " + h1.getTagName());
        }
    }

    /**
     * KeyDown type the input html elements on the page. Works only with input
     * and textarea elements.
     *
     * @param xpath
     *            the XPath
     * @param newValue
     *            the new value
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void keyDown(String xpath, char newValue)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        if (h1 instanceof HtmlTextInput || h1 instanceof HtmlPasswordInput
                || h1 instanceof HtmlFileInput || h1 instanceof HtmlTextArea) {
            page.setFocusedElement(h1);
            page.pressAccessKey(newValue);
        } else {
            fail("Type method not supported for element " + h1.getTagName());
        }
    }

    /**
     * MouseOver for the input html elements on the page. Works only with input
     * and textarea elements.
     *
     * @param xpath
     *            the XPath
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void mouseOver(String xpath)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        if (h1 instanceof HtmlTextInput || h1 instanceof HtmlPasswordInput
                || h1 instanceof HtmlFileInput || h1 instanceof HtmlTextArea) {
            h1.mouseOver();
        } else {
            fail("MouseOver method not supported for element "
                    + h1.getTagName());
        }
    }

    /**
     * Selects the new opinion from the select html element.
     *
     * @param xpath
     *            the XPath
     * @param optionText
     *            the opinion description text
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void select(String xpath, String optionText)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        final HtmlSelect select = (HtmlSelect) getHtmlElement(xpath);
        boolean checkByValue = false;
        String checkedText = optionText.startsWith("label=") ? optionText
                .substring(6) : optionText;
        if (optionText.startsWith("value=")) {
            checkedText = optionText.substring(6);
            checkByValue = true;
        }
        assertNotNull("Element " + xpath + " not found on the page", select);

        for (final HtmlElement next : select.getAllHtmlChildElements()) {
            if (next.getTagName().equals("option")) {
                if (checkByValue) {
                    if (next.getAttribute("value").equals(checkedText)) {
                        select.setSelectedAttribute(next.getAttribute("value"),
                                true);
                        return;
                    }
                } else {
                    final String attText = next.asText();
                    if (attText != null && attText.equals(checkedText)) {
                        select.setSelectedAttribute(next.getAttribute("value"),
                                true);
                        return;
                    }
                }
            }
        }
        fail("Element " + optionText + " not found");
    }

    @SuppressWarnings("unchecked")
    protected HtmlElement getHtmlElement(String xpath) {
        if (xpath != null && xpath.startsWith("link=")) {
            final List<HtmlElement> elements = (List<HtmlElement>) page
                    .getByXPath("//a");
            for (HtmlElement element : elements) {
                if (element.asText().equals(xpath.substring(5))) {
                    return element;
                }
            }
            return null;
        } else if (xpath != null && xpath.startsWith("//")) {
            return (HtmlElement) page.getFirstByXPath(xpath);
        }
        try {
            return page.getHtmlElementById(xpath);
        } catch (ElementNotFoundException ex) {
            try {
                return page.getElementsByName(xpath).get(0);
            } catch (IndexOutOfBoundsException exi) {
                return null;
            }
        }
    }

    /**
     * Checks the content of the html elements.
     *
     * @param xpath
     *            the XPath
     * @param expected
     *            the expected text
     */
    protected void assertText(String xpath, String expected) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        assertEquals("Text not found in path " + xpath, expected, h1.asText());
    }

    /**
     * Checks page title.
     *
     * @param expected
     *            the expected text
     */
    protected void assertTitleEquals(String expected) {
        assertText("//head/title", expected);
    }

    /**
     * Alias for type.
     *
     * @param xpath
     *            the XPath
     * @param expected
     *            the expected text
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void setTextField(String xpath, String expected)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        type(xpath, expected);
    }

    /**
     * Returns the page source html.
     *
     * @return the page source
     */
    protected String getPageSource() {
        return page.getWebResponse().getContentAsString().trim();
    }

    /**
     * Returns the page status.
     *
     * @return the page status
     */
    protected String getPageStatus() {
        return page.getWebResponse().getStatusMessage();
    }

    /**
     * Encodes string to url.
     *
     * @param strToEncode
     *            url to encode
     * @throws UnsupportedEncodingException
     *             URL format error
     * @return the encoded string
     */
    protected String encodeToUrl(String strToEncode)
            throws UnsupportedEncodingException {
        return URLEncoder.encode(strToEncode, "UTF-8");
    }

    /**
     * Make submit for the first form.
     *
     * @throws IOException
     *             IO error
     */
    @SuppressWarnings("unchecked")
    protected void submit() throws IOException {
        final List<HtmlForm> forms = (List<HtmlForm>) page.getByXPath("//form");
        if (!forms.isEmpty()) {
            changePage(forms.get(0).click());
        }
    }

    /**
     * Checks the regexp content of the html elements.
     *
     * @param xpath
     *            the XPath
     * @param expected
     *            the expected text
     */
    protected void assertTextRegexp(String xpath, String expected) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        assertTrue("Text " + expected + " not found in path " + xpath, h1
                .asText().matches(expected));
    }

    /**
     * Returns the text inside html element.
     *
     * @param xpath
     *            the XPath
     * @return the text inside html element
     */
    protected String getText(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        return h1.asText().replaceAll("[\\r\\n]", " ").replaceAll("\\s+", " ");
    }

    /**
     * Returns the text inside table.
     *
     * @param xpath
     *            the XPath
     * @return the text inside table
     */
    protected String getTable(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath.replaceAll(
                "\\.\\d+\\.\\d+$", ""));
        assertNotNull("Element " + xpath + " not found on the page", h1);
        final Matcher matcher = Pattern.compile("\\.(\\d+)\\.(\\d+)$").matcher(xpath);
        assertTrue("Table data not found in " + xpath, matcher.find());
        final Integer trLevel = Integer.valueOf(matcher.group(1));
        final Integer tdLevel = Integer.valueOf(matcher.group(2));

        final HtmlElement h2 = getHtmlElement(xpath.replaceAll(
                "\\.\\d+\\.\\d+$", "")
                + "//tr[" + (trLevel + 1) + "]/td[" + (tdLevel + 1) + "]");
        assertNotNull("Element " + xpath + " not found on the page", h2);
        return h2.asText();
    }

    /**
     * Returns the text inside html element.
     *
     * @param xpath
     *            the XPath
     * @return the text inside html element
     */
    protected String getValue(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        return h1.asText();
    }

    /**
     * Returns the text for the select html element.
     *
     * @param xpath
     *            the XPath
     * @return the text inside html element
     */
    protected String getSelectedLabel(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
        return h1.asText();
    }

    /**
     * Returns the value inside html input element.
     *
     * @param xpath
     *            the XPath
     * @return the value attribute content
     */
    protected String storeValue(String xpath) {
        return storeAttribute(xpath + "@value");
    }

    /**
     * Checks availability of the expression.
     *
     * @param xpath
     *            the XPath
     */
    protected void assertElementPresent(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", h1);
    }

    /**
     * Returns availability of the expression.
     *
     * @param xpath
     *            the XPath
     * @return the element present
     */
    protected boolean isElementPresent(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        return h1 != null;
    }

    /**
     * Checks is text present on the page.
     *
     * @param text
     *            text to check.
     * @return the text present
     */
    protected boolean isTextPresent(String text) {
        return page.asText().contains(text);
    }

    /**
     * Checks availability of the expression with custom error message.
     *
     * @param comment
     *            the warning message
     * @param xpath
     *            the XPath
     */
    protected void assertElementPresent(String comment, String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNotNull(comment, h1);
    }

    /**
     * Checks absent of the expression.
     *
     * @param xpath
     *            the XPath
     */
    protected void assertElementNotPresent(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath);
        assertNull("Unexpected element on page " + xpath, h1);
    }

    /**
     * Waits for element present on the page.
     *
     * @param xpath
     *            the XPath
     * @throws InterruptedException
     *             in case of external interruption
     */
    protected void waitForElementPresent(String xpath)
            throws InterruptedException {
        final int tryCount = 4;
        final int timeDelay = 500;
        // try 4 times to wait .5 second each for filling the page.
        for (int count = 0; count < tryCount; count += 1) {
            final HtmlElement h1 = getHtmlElement(xpath);
            if (h1 != null) {
                return;
            }
            synchronized (page) {
                page.wait(timeDelay);
            }
        }
        fail("Element " + xpath + " not found on the page");
    }

    /**
     * Checks availability of the expression with custom error message.
     *
     * @param expected
     *            title
     */
    protected void assertTitle(String expected) {
        final HtmlElement h1 = getHtmlElement("//head/title");
        assertNotNull("Element title not found on the page", h1);
        assertEquals("Title not found on the page ", expected, h1.asText());
    }

    /**
     * Logs the message.
     *
     * @param message
     *            to be logging
     */
    protected void echo(String message) {
        LOG.info(message);
    }

    /**
     * Emulates user's click for the html element.
     *
     * @param xpath
     *            the XPath
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void click(String xpath) throws FailingHttpStatusCodeException,
            MalformedURLException, IOException {
        HtmlElement link = (HtmlElement) getHtmlElement(xpath);
        assertNotNull("Element " + xpath + " not found on the page", link);
        if (link instanceof HtmlCheckBoxInput) {
            if (((HtmlCheckBoxInput) link).isChecked()) {
                ((HtmlCheckBoxInput) link).setChecked(false);
            } else {
                ((HtmlCheckBoxInput) link).setChecked(true);
            }
            return;
        } else if (link instanceof HtmlRadioButtonInput) {
            ((HtmlRadioButtonInput) link).setChecked(true);
            return;
        }

        changePage((Page) link.click());
    }

    /**
     * The alias for the {@link #click(String)}.
     *
     * @param xpath
     *            the XPath
     * @throws FailingHttpStatusCodeException
     *             http errors
     * @throws MalformedURLException
     *             URL format error
     * @throws IOException
     *             IO error
     */
    protected void clickAndWait(String xpath)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        click(xpath);
    }

    /**
     * Returns to the previous page.
     *
     * @throws IOException
     *             IO error
     * @throws MalformedURLException
     *             URL format error
     * @throws FailingHttpStatusCodeException
     *             http errors
     */
    protected void goBackAndWait() throws FailingHttpStatusCodeException,
            MalformedURLException, IOException {
        if (!pageHistory.isEmpty()) {
            pageHistory.pop();
        }
        if (!pageHistory.isEmpty()) {
            page = (HtmlPage) pageHistory.peek();
        }
    }

    /**
     * Checks the count of elements for the XPath expression.
     *
     * @param xpath
     *            the XPath
     * @param count
     *            the number of elements to check
     */
    @SuppressWarnings("unchecked")
    protected void assertXpathCount(String xpath, Integer count) {
        final List<HtmlElement> elements = (List<HtmlElement>) page
                .getByXPath(xpath);
        assertEquals("Showld be equal", count, Integer.valueOf(elements.size()));
    }

    /**
     * Return the XPath count for the html element.
     *
     * @param xpath
     *            the XPath expression
     * @return the count of items
     */
    @SuppressWarnings("unchecked")
    protected Integer storeXpathCount(String xpath) {
        final List<HtmlElement> elements = (List<HtmlElement>) page
                .getByXPath(xpath);
        return Integer.valueOf(elements.size());
    }

    /**
     * Checks attribute value inside html element. Example:
     * assertAttribute("//body/form/div[3]/div/div@style",
     * "background-image: url(/SiteManager/admin/images/bg_addedit.gif);");
     *
     * @param xpath
     *            the XPath
     * @param expected
     *            the attribute value
     */
    protected void assertAttribute(String xpath, String expected) {
        final HtmlElement h1 = getHtmlElement(xpath.replaceAll("\\@\\w+$", ""));
        assertNotNull("Element " + xpath + " not found on the page", h1);
        final Matcher matcher = Pattern.compile("\\@(\\w+$)").matcher(xpath);
        assertTrue("Attribute not found in " + xpath, matcher.find());
        assertEquals("Text not found in attribute " + matcher.group(1),
                expected, h1.getAttribute(matcher.group(1)).trim());
    }

    /**
     * Checks attribute value inside html element. Example:
     * assertAttribute("//body/form/div[3]/div/div@style",
     * "background-image: url(/SiteManager/admin/images/bg_addedit.gif);");
     *
     * @param xpath
     *            the XPath
     * @param expected
     *            the attribute value
     * @throws InterruptedException
     *             in case of external interrupt
     */
    protected void waitForAttribute(String xpath, String expected)
            throws InterruptedException {
        final int tryCount = 8;
        final int timeDelay = 500;
        String actual = "";
        // try 4 times to wait .5 second each for filling the page.
        for (int count = 0; count < tryCount; count += 1) {
            final HtmlElement h1 = getHtmlElement(xpath.replaceAll("\\@\\w+$",
                    ""));
            final Matcher matcher = Pattern.compile("\\@(\\w+$)").matcher(xpath);
            if (h1 != null && matcher != null && matcher.find()) {
                actual = h1.getAttribute(matcher.group(1)).trim();
            }
            if (expected.equals(actual)) {
                return;
            }
            synchronized (page) {
                page.wait(timeDelay);
            }
        }
        assertEquals("Attribute not matched", expected, actual);
    }

    /**
     * Selects the frame.
     *
     * @param name
     *            the frame name
     * @throws IOException
     *             in case of IO errors
     * @throws MalformedURLException
     *             HTML exception
     * @throws FailingHttpStatusCodeException
     *             frame error
     */
    protected void selectFrame(String name)
            throws FailingHttpStatusCodeException, MalformedURLException,
            IOException {
        if ("relative=up".equals(name)) {
            goBackAndWait();
            return;
        }
        for (FrameWindow frameWindow : page.getFrames()) {
            if (frameWindow.getName().equalsIgnoreCase(name)) {
                changePage((HtmlPage) frameWindow.getEnclosedPage());
            }
        }
        assertNotNull("Frame " + name + " not found on the page", page);
    }

    /**
     * Gets the html element attribute. Example: String attr =
     * storeAttribute("//body/form/div[3]/div/div@style");
     *
     * @param xpath
     *            the XPath
     * @return the attribute
     */
    protected String storeAttribute(String xpath) {
        final HtmlElement h1 = getHtmlElement(xpath.replaceAll("\\@\\w+$", ""));
        assertNotNull("Element " + xpath + " not found on the page", h1);
        final Matcher matcher = Pattern.compile("\\@(\\w+$)").matcher(xpath);
        assertTrue("Attribute not found in " + xpath, matcher.find());
        return h1.getAttribute(matcher.group(1).trim());
    }

    /**
     * Returns the array of found elements in string.
     *
     * @param regexp
     *            regular expresion
     * @param str
     *            source string
     * @return the array of found strings
     */
    protected String[] storeRegexp(String regexp, String str) {
        String[] result;
        final Matcher matcher = Pattern.compile(regexp).matcher(str);
        assertTrue("Regexp not found in " + str, matcher.find());
        result = new String[matcher.groupCount() + 1];
        for (int index = 0; index < matcher.groupCount() + 1; index += 1) {
            result[index] = matcher.group(index);
        }
        return result;
    }

    /**
     * Load a page with the specified HTML using the current browser version,
     * and asserts the alerts equal the expected alerts.
     *
     * @param html
     *            the HTML to use
     * @throws Exception
     *             if something goes wrong
     */
    protected final void loadPageFromString(final String html) throws Exception {
        final File f = File.createTempFile("TEST" + '_', ".html");
        FileUtils.writeStringToFile(f, html, "ISO-8859-1");
        final URL url = new URL("file://" + f.getCanonicalPath());
        page = webClient.getPage(url);
    }

    /**
     * Select the specified set of options in the select element with the
     * provided name.
     *
     * @param formName
     *            name of the form
     * @param selectName
     *            name of the select box
     * @param index
     *            the 0-based index of the select element when multiple select
     *            elements are expected.
     * @param options
     *            set of options to select.
     */
    protected void selectOptions(String formName, String selectName, int index,
            String[] options) {
        final HtmlForm form = page.getFormByName(formName);
        final List<HtmlSelect> sels = form.getSelectsByName(selectName);
        if (sels == null || sels.size() < index + 1) {
            throw new RuntimeException("Did not find select with name ["
                    + selectName + "] at index " + index);
        }
        final HtmlSelect sel = (HtmlSelect) sels.get(index);
        if (!sel.isMultipleSelectEnabled() && options.length > 1) {
            throw new RuntimeException("Multiselect not enabled");
        }
        for (String option : options) {
            boolean found = false;
            for (HtmlOption opt : sel.getOptions()) {
                if (opt.getValueAttribute().equals(option)) {
                    sel.setSelectedAttribute(opt, true);
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new RuntimeException("Option " + option + " not found");
            }
        }
    }

    /**
     * Stub for method.
     *
     * @param time
     *            in milliseconds
     */
    protected void waitForPageToLoad(String time) {
        // Dummy method.
    }
}
